local stairs_repl
if minetest.get_modpath("stairs") then
    stairs_repl = "stairs:stair_underworld_brick"
else
    stairs_repl = "underworld:brick"
end

local node_replacements = {
    ["air"] = "air",
    
    ["default:cobble"] = "underworld:brick",
    ["default:mossycobble"] = "underworld:brick",
    ["stairs:stair_cobble"] = stairs_repl,
    ["stairs:stair_mossycobble"] = stairs_repl,
    
    ["default:stone_with_coal"] = "default:lava_source",
    ["default:stone_with_iron"] = "default:steelblock",
    ["default:stone_with_copper"] = "default:copperblock",
    ["default:stone_with_tin"] = "default:tinblock",
    ["default:stone_with_gold"] = "default:goldblock",
    ["default:stone_with_mese"] = "default:mese",
    ["default:stone_with_diamond"] = "default:diamondblock",
    ["moreores:mineral_silver"] = "moreores:silver_block",
    ["moreores:mineral_mithril"] = "moreores:mithril_block",
    ["technic:mineral_chromium"] = "technic:chromium_block",
    ["technic:mineral_zinc"] = "technic:zinc_block",
    ["technic:mineral_lead"] = "technic:lead_block",
    
    ["default:gravel"] = "underworld:gravel",
    ["default:silver_sand"] = "underworld:gravel",
    ["default:water_source"] = "default:lava_source",
    ["default:water_flowing"] = "default:lava_source",
    ["default:lava_source"] = "default:lava_source",
    ["default:lava_flowing"] = "default:lava_source",
    ["default:mese"] = "underworld:crystal",
    
    ["bedrock2:bedrock"] = "bedrock2:bedrock",
}

local repl_cids = {}
for node, repl in pairs(node_replacements) do
    if minetest.registered_nodes[node] and minetest.registered_nodes[repl] then
        repl_cids[minetest.get_content_id(node)] = minetest.get_content_id(repl)
    end
end

local overgenerated_nodes = {
    "air",
    "default:lava_source",
    "default:lava_flowing",
    "default:water_source",
    "default:water_flowing",
    "default:cobble",
    "default:mossycobble",
    "stairs:stair_cobble",
    "stairs:stair_mossycobble",
}
local overgenerated_cids = {}
for _, o_node in pairs(overgenerated_nodes) do
    if minetest.registered_nodes[o_node] then
        overgenerated_cids[minetest.get_content_id(o_node)] = true
    end
end

local c_underworld_exploding_stone
if minetest.get_modpath("tnt") then
    c_underworld_exploding_stone = minetest.get_content_id("underworld:exploding_stone")
end
local c_underworld_brick_stair
if minetest.get_modpath("stairs") then
    c_underworld_brick_stair = minetest.get_content_id("stairs:stair_underworld_brick")
end

local c_underworld_stone = minetest.get_content_id("underworld:stone")
local c_underworld_brick = minetest.get_content_id("underworld:brick")
local c_chest = minetest.get_content_id("default:chest")
local c_obsidian = minetest.get_content_id("default:obsidian")

local loot_lists = {
    {
        "default:coalblock",
        "default:steelblock",
        "default:copperblock",
        "default:tinblock",
        "default:goldblock",
        "default:mese",
        "default:diamondblock",
        "underworld:portal_base",
        "underworld:crystal",
    },
}
if minetest.get_modpath("moreores") then
    table.insert(loot_lists, {
        "moreores:silver_block",
        "moreores:mithril_block",
    })
end
if minetest.get_modpath("technic") then
    table.insert(loot_lists, {
        "technic:chromium_block",
        "technic:zinc_block",
        "technic:lead_block",
        "technic:uranium35_block",
    })
end

loot = {}
for _, list in pairs(loot_lists) do
    for _, item in pairs(list) do
        table.insert(loot, item)
    end
end

minetest.set_gen_notify({dungeon = true})

local underworld_vm_data = {}
minetest.register_on_generated(function(minp, maxp)
    if minp.y > underworld.depth then
        return
    end
    
    local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
    local area = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
    vm:get_data(underworld_vm_data)
    
    local chunk_area = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
    
    -- Generate underworld
    for z = emin.z, emax.z do
        for y = math.min(emin.y, underworld.depth), emax.y do
            for x = emin.x, emax.x do
                local vi = area:index(x, y, z)
                if y > underworld.depth - underworld.barrier_size then
                    underworld_vm_data[vi] = c_obsidian
                else
                    local cid = underworld_vm_data[vi]
                    if chunk_area:contains(x, y, z) or overgenerated_cids[cid] then
                        local repl_cid = repl_cids[cid]
                        if repl_cid then
                            underworld_vm_data[vi] = repl_cid
                        elseif c_underworld_exploding_stone and math.random(1, 2000) == 1 then
                            underworld_vm_data[vi] = c_underworld_exploding_stone
                        else
                            underworld_vm_data[vi] = c_underworld_stone
                        end
                    end
                end
            end
        end
    end
    
    -- Add chest to dungeon
    local dungeon_rooms = minetest.get_mapgen_object("gennotify").dungeon or {}
    local chest_pos, chest_dir
    while #dungeon_rooms > 0 and not chest_pos do
        local pos = table.remove(dungeon_rooms, math.random(1, #dungeon_rooms))
        -- Find floor
        while underworld_vm_data[area:indexp(vector.subtract(pos, {x = 0, y = 1, z = 0}))] == minetest.CONTENT_AIR do
            pos.y = pos.y - 1
        end
        
        local dirs = {
            {x = 1, y = 0, z = 0},
            {x = -1, y = 0, z = 0},
            {x = 0, y = 0, z = 1},
            {x = 0, y = 0, z = -1},
        }
        local floor_pos = table.copy(pos)
        while #dirs > 0 and not chest_pos do
            pos = table.copy(floor_pos)
            local dir = table.remove(dirs, math.random(1, #dirs))
            while underworld_vm_data[area:indexp(vector.add(pos, dir))] == minetest.CONTENT_AIR do
                pos = vector.add(pos, dir)
            end
            
            local perp_dirs = {}
            if pos.x ~= 0 then
                perp_dirs = {
                    {x = 0, y = 0, z = 1},
                    {x = 0, y = 0, z = -1},
                }
            else
                perp_dirs = {
                    {x = 1, y = 0, z = 0},
                    {x = -1, y = 0, z = 0},
                }
            end
            
            local wall_pos = table.copy(pos)
            for _, perp_dir in pairs(perp_dirs) do
                pos = table.copy(wall_pos)
                while underworld_vm_data[area:indexp(vector.add(pos, perp_dir))] == minetest.CONTENT_AIR do
                    pos = vector.add(pos, perp_dir)
                end
                
                -- Check if corner spot is not next to or on top of stairs
                local num_nearby_brick = 0
                local by_stairs = false
                local space_above = false
                local on_floor = false
                for z = -1, 1 do
                    for y = -1, 1 do
                        for x = -1, 1 do
                            local nearby_cid = underworld_vm_data[area:indexp(vector.add(pos, {x = x, y = y, z = z}))]
                            if nearby_cid == c_underworld_brick or nearby_cid == c_underworld_brick_stair then
                                num_nearby_brick = num_nearby_brick + 1
                            end
                            if x == 0 and z == 0 then
                                if y == -1 and nearby_cid == c_underworld_brick then
                                    on_floor = true
                                elseif y == 1 and nearby_cid == minetest.CONTENT_AIR then
                                    space_above = true
                                end
                            end
                            if math.abs(x) + math.abs(z) == 1 and nearby_cid == c_underworld_brick_stair then
                                by_stairs = true
                            end
                        end
                    end
                end
                
                if on_floor and space_above and num_nearby_brick >= 19 and not by_stairs then
                    chest_pos = pos
                    chest_dir = perp_dir
                    break
                end
            end
        end
    end
    
    vm:set_data(underworld_vm_data)
    vm:set_lighting({day = 0, night = 0})
    vm:calc_lighting()
    vm:update_liquids()
    vm:write_to_map()
    
    -- Fill dungeon loot chest
    if chest_pos then
        minetest.set_node(chest_pos, {name = "default:chest", param2 = minetest.dir_to_facedir(chest_dir)})
        local chest_inv = minetest.get_meta(chest_pos):get_inventory()
        for i = 1, chest_inv:get_size("main") do
            chest_inv:set_stack("main", i, ItemStack(loot[math.random(1, #loot)]))
        end
    end
end)